#include <botan/auto_rng.h>
#include <botan/hex.h>
#include <totp/totpManager.h>
#include <utils/functions.h>

namespace totp {
TOTPManager::TOTPManager(const std::filesystem::path& filepath, utils::CipherAlgorithm algorithm,
	const Botan::SymmetricKey &encryptionKey, QObject* parent) : QObject(parent),
		filepath(QString::fromStdString(filepath.string())),
		encryptionKey(encryptionKey) {
	algos = utils::createStringFromAlgos(algorithm);
	loadTOTPs();
}

void TOTPManager::addTOTP(const TOTP &totp) {
	totps.push_back(totp);
}

void TOTPManager::deleteTOTP(const TOTP &totp) {
	auto it = std::ranges::find_if(totps, [&totp](const auto& el) {
		return totp.key == el.key &&  totp.label == el.label &&  totp.period == el.period
		&& el.digits == totp.digits && totp.algorithm == el.algorithm;
	});
	totps.erase(it);
}

const std::vector<TOTP> & TOTPManager::getTOTPs() const {
	return totps;
}

void TOTPManager::saveTOTPs() {
	QJsonArray jsonArray;
	for (const auto& totp : totps) {
		QJsonObject jsonObj;
		jsonObj["label"] = totp.label;
		jsonObj["key"] = QString::fromStdString(Botan::hex_encode(totp.key));
		jsonObj["algorithm"] = totp.algorithm;
		jsonObj["digits"] = totp.digits;
		jsonObj["period"] = totp.period;
		jsonArray.append(jsonObj);
	}

	QJsonDocument jsonDoc(jsonArray);
	QString plaintext = jsonDoc.toJson(QJsonDocument::Compact);
	QByteArray ciphertext = encrypt(plaintext.toUtf8());
	QFile file(filepath);
	if (file.open(QIODevice::WriteOnly)) {
		file.write(ciphertext);
		file.close();
	}
}

void TOTPManager::loadTOTPs() {
	QFile file(filepath);
	if (file.open(QIODevice::ReadOnly)) {
		QByteArray ciphertext = file.readAll();
		file.close();
		QByteArray plaintext = decrypt(ciphertext);
		QJsonDocument jsonDoc = QJsonDocument::fromJson(plaintext);
		QJsonArray jsonArray = jsonDoc.array();
		totps.clear();
		for (const QJsonValue& value : jsonArray) {
			QJsonObject jsonObj = value.toObject();
			TOTP totp(
					jsonObj["label"].toString(),
					Botan::SymmetricKey(Botan::hex_decode(jsonObj["key"].toString().toStdString())),
					jsonObj["algorithm"].toString(),
					jsonObj["digits"].toInt(),
					jsonObj["period"].toInt()
					);
			totps.push_back(totp);
		}
	}
}

QByteArray TOTPManager::encrypt(const QByteArray &plaintext) {
	Botan::AutoSeeded_RNG rng;
	const auto ivSize = algos == "GOST-28147-89/CBC" ? 8 : 16;
	std::unique_ptr<Botan::Cipher_Mode> enc(Botan::Cipher_Mode::create(algos, Botan::Cipher_Dir::Encryption));
	enc->set_key(encryptionKey);
	Botan::InitializationVector iv(rng, ivSize);
	Botan::secure_vector<uint8_t> pt(reinterpret_cast<const uint8_t*>(plaintext.data()), reinterpret_cast<const uint8_t*>(plaintext.data()) + plaintext.size());
	enc->start(iv);
	enc->finish(pt);
	QByteArray l(reinterpret_cast<const char*>(iv.bits_of().data()), iv.size());
	return l+QByteArray(reinterpret_cast<const char*>(pt.data()), pt.size());
}

QByteArray TOTPManager::decrypt(const QByteArray &ciphertext) {
	std::unique_ptr dec(Botan::Cipher_Mode::create(algos, Botan::Cipher_Dir::Decryption));
	dec->set_key(encryptionKey);
	const auto ivSize = algos == "GOST-28147-89/CBC" ? 8 : 16;
	Botan::secure_vector<uint8_t> ct(reinterpret_cast<const uint8_t*>(ciphertext.data()), reinterpret_cast<const uint8_t*>(ciphertext.data()) + ciphertext.size());
	Botan::SecureVector<uint8_t> iv(ct.begin(), ct.begin()+ivSize);
	ct.erase(ct.begin(), ct.begin()+ivSize);
	dec->start(iv);
	dec->finish(ct);

	return QByteArray(reinterpret_cast<const char*>(ct.data()), ct.size());
}
}
